/**     
 * @Title: ParameterBaseWebSessionManager.java   
 * @Package com.yitong.spring.web   
 * @Description: TODO
 * @author weiwei 
 * @date 2017年7月21日 下午6:28:59   
 * @version V1.0     
 */
package com.xnkf.common.shiro.web;

import java.io.Serializable;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.shiro.session.ExpiredSessionException;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.DelegatingSession;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.WebSessionKey;
import org.apache.shiro.web.session.mgt.WebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xnkf.common.shiro.session.RedisSessionDAO;

/**
 * @ClassName: ParameterBaseWebSessionManager
 * @Description: TODO
 * @author weiwei
 * @date 2017年7月21日 下午6:28:59
 * 
 */
public class ParameterBaseWebSessionManager extends DefaultSessionManager implements WebSessionManager {

	private static final Logger LOGGER = LoggerFactory.getLogger(ParameterBaseWebSessionManager.class);

	public static final String SESSION_ID_NAME = "sid";

	private Cookie sessionIdCookie;

	private boolean sessionIdCookieEnabled;

	public ParameterBaseWebSessionManager() {
		Cookie cookie = new SimpleCookie(SESSION_ID_NAME);
		cookie.setHttpOnly(true); // more secure, protects against XSS attacks
		this.sessionIdCookie = cookie;
		this.sessionIdCookieEnabled = true;
		setSessionDAO(new RedisSessionDAO());
	}

	public Cookie getSessionIdCookie() {
		return sessionIdCookie;
	}

	public boolean isSessionIdCookieEnabled() {
		return sessionIdCookieEnabled;
	}

	private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
		if (currentId == null) {
			String msg = "sessionId cannot be null when persisting for subsequent requests.";
			throw new IllegalArgumentException(msg);
		}
		Cookie template = getSessionIdCookie();
		Cookie cookie = new SimpleCookie(template);
		String idString = currentId.toString();
		cookie.setValue(idString);
		cookie.saveTo(request, response);
		LOGGER.trace("Set session ID cookie for session with id {}", idString);
	}

	private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) {
		getSessionIdCookie().removeFrom(request, response);
	}

	private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
		String sid = WebUtils.getCleanParam(request, SESSION_ID_NAME);
		if (StringUtils.isBlank(sid)) {
			sid = getSessionIdCookieValue(request, response);
		}
		return sid;
	}

	private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
		if (!isSessionIdCookieEnabled()) {
			LOGGER.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
			return null;
		}
		if (!(request instanceof HttpServletRequest)) {
			LOGGER.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
			return null;
		}
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
	}

	protected Session createExposedSession(Session session, SessionContext context) {
		if (!WebUtils.isWeb(context)) {
			return super.createExposedSession(session, context);
		}
		ServletRequest request = WebUtils.getRequest(context);
		ServletResponse response = WebUtils.getResponse(context);
		SessionKey key = new WebSessionKey(session.getId(), request, response);
		return new DelegatingSession(this, key);
	}

	protected Session createExposedSession(Session session, SessionKey key) {
		if (!WebUtils.isWeb(key)) {
			return super.createExposedSession(session, key);
		}
		ServletRequest request = WebUtils.getRequest(key);
		ServletResponse response = WebUtils.getResponse(key);
		SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
		return new DelegatingSession(this, sessionKey);
	}

	/**
	 * Stores the Session's ID, usually as a Cookie, to associate with future
	 * requests.
	 * 
	 * @param session
	 *            the session that was just {@link #createSession created}.
	 */
	@Override
	protected void onStart(Session session, SessionContext context) {
		super.onStart(session, context);
		if (!WebUtils.isHttp(context)) {
			LOGGER.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " + "pair. No session ID cookie will be set.");
			return;

		}
		HttpServletRequest request = WebUtils.getHttpRequest(context);
		HttpServletResponse response = WebUtils.getHttpResponse(context);
		if (isSessionIdCookieEnabled()) {
			Serializable sessionId = session.getId();
			storeSessionId(sessionId, request, response);
		} else {
			LOGGER.debug("Session ID cookie is disabled.  No cookie has been set for new session with id {}", session.getId());
		}
		request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
		request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
	}

	@Override
	public Serializable getSessionId(SessionKey key) {
		Serializable id = super.getSessionId(key);
		if (id == null && WebUtils.isWeb(key)) {
			ServletRequest request = WebUtils.getRequest(key);
			ServletResponse response = WebUtils.getResponse(key);
			id = getSessionId(request, response);
		}
		return id;
	}

	protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
		return getReferencedSessionId(request, response);
	}

	@Override
	protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
		super.onExpiration(s, ese, key);
		onInvalidation(key);
	}

	@Override
	protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
		super.onInvalidation(session, ise, key);
		onInvalidation(key);
	}

	private void onInvalidation(SessionKey key) {
		ServletRequest request = WebUtils.getRequest(key);
		if (request != null) {
			request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
		}
		if (WebUtils.isHttp(key)) {
			LOGGER.debug("Referenced session was invalid.  Removing session ID cookie.");
			removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
		} else {
			LOGGER.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response "
					+ "pair. Session ID cookie will not be removed due to invalidated session.");
		}
	}

	@Override
	protected void onStop(Session session, SessionKey key) {
		super.onStop(session, key);
		if (WebUtils.isHttp(key)) {
			HttpServletRequest request = WebUtils.getHttpRequest(key);
			HttpServletResponse response = WebUtils.getHttpResponse(key);
			LOGGER.debug("Session has been stopped (subject logout or explicit stop).  Removing session ID cookie.");
			removeSessionIdCookie(request, response);
		} else {
			LOGGER.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response "
					+ "pair. Session ID cookie will not be removed due to stopped session.");
		}
	}

	public boolean isServletContainerSessions() {
		return false;
	}
}
